package com.yh.config;

import com.baidu.fsg.uid.UidGenerator;
import com.baidu.fsg.uid.impl.CachedUidGenerator;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yh.interceptor.*;
import com.yh.uid.DisposableWorkerIdAssigner;
import com.yh.utils.Constants;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.amqp.core.*;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.config.annotation.*;

import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;

@Configuration
@PropertySource("classpath:config.properties")
@MapperScan("com.yh.dao")
// 引人spring的定时任务
@EnableScheduling
/**
 * @author yuhuan
 * @date 2022/01/13
 * */
public class AppConfig implements WebMvcConfigurer {
    /**
     * redis连接工厂
     */
    @Autowired
    private LettuceConnectionFactory connectionFactory;

    /**
     * 将拦截器交给spring管理主要是为了让拦截器可以直接使用依赖注入
     */
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor());
        registry.addInterceptor(loginAuthentication());
        registry.addInterceptor(loginTimeInterceptor());
        registry.addInterceptor(idempotencyInterceptor());
    }

    /**
     * 幂等性拦截器
     */
    @Bean
    public IdempotencyInterceptor idempotencyInterceptor() {
        return new IdempotencyInterceptor();
    }

    /**
     * 登录请求拦截器
     */
    @Bean
    public LoginRequestInterceptor loginInterceptor() {
        return new LoginRequestInterceptor();
    }

    /**
     * 动态更新登录时间拦截器
     */
    @Bean
    public UpdateLoginTimeInterceptor loginTimeInterceptor() {
        return new UpdateLoginTimeInterceptor();
    }

    /**
     * 用户身份验证拦截器
     */
    @Bean
    public AuthenticationInterceptor loginAuthentication() {
        return new AuthenticationInterceptor();
    }

    /**
     * 频繁请求拦截器
     */
    @Bean
    public FrequentRequestInterceptor frequentRequestInterceptor() {
        return new FrequentRequestInterceptor();
    }

    /**
     * 序列化器
     */
    @Bean
    public ObjectMapper objectMapper() {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
        ObjectMapper objectMapperForBuild = builder.dateFormat(new SimpleDateFormat("yyyy-MM-dd")).build();
        return objectMapperForBuild;
    }

    /**
     * 用于图片的上传和下载（注意：这里需要使用固定的'multipartResolver'作为'Bean'的唯一标识）
     */
    @Bean
    public MultipartResolver multipartResolver() {
        // spring3.1之后推荐使用'StandardServletMultipartResolver'；3.1之前需要使用'CommonsMultipartResolver'
        StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
        return multipartResolver;
    }

    /**
     * 自定义序列化器的redis中心模板
     */
    @Bean
    public RedisTemplate redisTemplate() {
        // 创建中心模板
        RedisTemplate redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        // 设置序列化器
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }

    /**
     * 如果队列的生产者和消费者不在同一个模块，那么队列和交换机等"Bean"的声明最好不要
     * 放在其中的某个模块，而是放到这些模块都共享的地方声明
     * 原因（踩坑点）：
     * 经过实际案例测试，对于消息发布者而言，如果想要让队列声明成功，则必须要让其内部
     * 创建连接的方法得到执行。而单纯的将"Bean"对象交给容器管理并不会执行其创建连接的
     * 方法，必须得在业务层访问过"RabbitAdmin"实例或"RabbitTemplate"实例后才会执
     * 行创建连接的方法。所以，在消息发布者程序刚启动时，队列并不会马上被声明，此时如
     * 果启动消费者，将会找不到队列。而对于消息消费者而言，监听器会自动执行队列等组件
     * 的声明，但要求是必须在配置文件中有过"Bean"的声明。如果此时队列和交换机等"Bean"
     * 的声明是在发布端模块，则同样无法成功声明队列和交换机
     */
    @Configuration
    class RabbitMqConfiguration {

        /**
         * 创建消息队列的消息转换器，创建消息转换器的"bean"后，boot框架会自动帮我们设定发送与接收
         * 都用这个序列化
         */
        @Bean
        public Jackson2JsonMessageConverter converter() {
            return new Jackson2JsonMessageConverter();
        }

        /**
         * 用于支付的持久化队列
         */
        @Bean
        public Queue payQueue() {
            return QueueBuilder.durable(Constants.RabbitMqData.PAY_QUEUE_NAME).build();
        }

        /**
         * 用于支付的持久化交换机
         */
        @Bean
        public DirectExchange payExchange() {
            return new DirectExchange(Constants.RabbitMqData.PAY_EXCHANGE_NAME, true, false);
        }

        /**
         * 建立支付交换机和队列之间的绑定关系
         */
        @Bean
        public Binding payBinding() {
            return BindingBuilder.bind(payQueue()).to(payExchange()).with(Constants.RabbitMqData.PAY_BINDING_KEY);
        }

        /**
         * 订单模块专用持久化队列
         */
        @Bean
        public Queue orderQueue() {
            return QueueBuilder.durable(Constants.RabbitMqData.ORDER_QUEUE_NAME).build();
        }

        /**
         * 订单模块专用持久化交换机
         */
        @Bean
        public DirectExchange orderExchange() {
            return new DirectExchange(Constants.RabbitMqData.ORDER_EXCHANGE_NAME, true, false);
        }

        /**
         * 建立订单交换机和队列之间的绑定关系
         */
        @Bean
        public Binding orderBinding() {
            return BindingBuilder.bind(payQueue()).to(payExchange()).with(Constants.RabbitMqData.ORDER_BINDING_KEY);
        }

        /**
         * 延迟队列
         * 目前用于释放过期订单占用的座位
         */
        @Bean
        public Queue delayQueue() {
            return QueueBuilder.durable(Constants.RabbitMqData.DELAY_QUEUE_NAME).build();
        }

        /**
         * 延迟交换机
         * 目前用于释放过期订单占用的座位
         */
        @Bean
        public CustomExchange delayExchange() {
            Map<String, Object> params = new HashMap<>(16);
            params.put("x-delayed-type", "direct");
            return new CustomExchange(Constants.RabbitMqData.DELAY_EXCHANGE_NAME, "x-delayed-message", true, false, params);
        }

        /**
         * 建立延迟交换机和延迟队列之间的绑定关系
         * 目前用于释放过期订单占用的座位
         */
        @Bean
        public Binding delayBinding() {
            return BindingBuilder.bind(delayQueue()).to(delayExchange()).with(Constants.RabbitMqData.DELAY_BINDING_KEY).noargs();
        }

        /**
         * 用于预热影片信息的临时队列
         */
        @Bean
        public Queue preheatQueue() {
            return QueueBuilder.nonDurable(Constants.RabbitMqData.PREHEAT_QUEUE_NAME).build();
        }

        /**
         * 用于预热影片信息的临时交换机
         */
        @Bean
        public DirectExchange preheatExchange() {
            return new DirectExchange(Constants.RabbitMqData.PREHEAT_EXCHANGE_NAME, false, false);
        }

        /**
         * 建立预热交换机和队列之间的绑定关系
         */
        @Bean
        public Binding preheatBinding() {
            return BindingBuilder.bind(preheatQueue()).to(preheatExchange()).with(Constants.RabbitMqData.PREHEAT_BINDING_KEY);
        }
    }

    @Configuration
    class UidConfiguration {
        /**
         * ID分配器(依赖于数据库)，创建生成ID所必要的参数信息
         */
        @Bean("disposableWorkerIdAssigner")
        public DisposableWorkerIdAssigner disposableWorkerIdAssigner() {
            DisposableWorkerIdAssigner disposableWorkerIdAssigner = new DisposableWorkerIdAssigner();
            return disposableWorkerIdAssigner;
        }

        /**
         * uid生成器，通过内部的'getUID()'生成流水号
         */
        @Bean("cachedUidGenerator")
        public UidGenerator uidGenerator(DisposableWorkerIdAssigner disposableWorkerIdAssigner) {
            CachedUidGenerator cachedUidGenerator = new CachedUidGenerator();
            cachedUidGenerator.setWorkerIdAssigner(disposableWorkerIdAssigner);
            cachedUidGenerator.setEpochStr("2022-01-5");
            return cachedUidGenerator;
        }
    }
}
